/**
 * Card.java
 * 
 * (C) 2011 - Mischa Spelt (m.spxxxelt@gmail.com - remove xxx)
 * 
 */
package com.mischa.doedeljas;

/**
 * Represents a card in the deck.
 * 
 * @author Mischa Spelt
 * 
 */
public class Card {
	/**
	 * Represents the value of the card.
	 * 
	 * @author Mischa Spelt
	 * 
	 */
	public static class CardRank
	{
		public static final CardRank SEVEN = new CardRank(1, "7");

		public static final CardRank EIGHT = new CardRank(2, "8");

		public static final CardRank NINE = new CardRank(3, "9");

		public static final CardRank TEN = new CardRank(4, "10");

		public static final CardRank JACK = new CardRank(5, "jack");

		public static final CardRank QUEEN = new CardRank(6, "queen");

		public static final CardRank KING = new CardRank(7, "king");

		public static final CardRank ACE = new CardRank(8, "ace");

		/**
		 * Convert an integer value to a card rank, where 1 = seven, 2 = eight, ....,
		 * 7 = king, 8 = ace.
		 * 
		 * @param value
		 * 		The integer value to convert (1 - 7)
		 * 
		 * @return
		 * 		The rank corresponding to the integer value
		 * 
		 * @throws IllegalArgumentException
		 * 		If value < 1 || value > 7.
		 */
		public static CardRank fromInteger(int value)
				throws IllegalArgumentException
		{
			switch (value)
			{
			case 1:
				return SEVEN;
			case 2:
				return EIGHT;
			case 3:
				return NINE;
			case 4:
				return TEN;
			case 5:
				return JACK;
			case 6:
				return QUEEN;
			case 7:
				return KING;
			case 8:
				return ACE;
			default:
				throw new IllegalArgumentException("Invalid suit: " + value);
			}
		}

		/**
		 * Define the scores for 7, 8, 9, 10, J, Q, K, A in a normal suit.
		 */
		private static final int[] scores = new int[] 
				{ 0, 0, 0, 10, 2, 3, 4, 11 };
		
		/**
		 * Define the ranking of the cards (7 < 8 < 9 < 10 < J < Q < K < A) in a normal suit
		 */
		private static final int[] comparisonValues = new int[] 
				{ 1, 2, 3, 7, 4, 5, 6, 8 };
		
		/**
		 * The integer representation of the rank.
		 */
		private final int _value;
		
		/**
		 * The name of the rank.
		 */
		private final String _name;

		/**
		 * Private constructor
		 * 
		 * @param ID
		 * 		The integer representation of the rank
		 * 
		 * @param name
		 * 		The name of the rank.
		 */
		private CardRank(int ID, String name)
		{
			_value = ID;
			this._name = name;
		}

		/**
		 * Get an integer value that can be used to compare cards by rank.
		 * 
		 * @param isTrump
		 * 		Whether the card is of trump suit (in which case jack and nine have 
		 * 		the highest rank).
		 * 
		 * @return
		 * 		an integer value that can be used to compare a card of another rank.
		 */
		public int comparisonValue(boolean isTrump)
		{
			if (isTrump)
			{
				if (_value == JACK._value)
				{
					return 20;
				} 
				else if (_value == NINE._value) 
				{
					return 19;
				}
				else 
				{
					return 10 + comparisonValues[_value - 1];
				}
			} 
			else 
			{
				return comparisonValues[_value - 1];
			}
		}

		/**
		 * Get the score for the card
		 * 
		 * @param isTrump
		 * 		Whether the card is of trump suit (in which case the 
		 * 		jack and nine will be worth 20 and 14 points, respectively).
		 *  
		 * @return
		 * 		The score for this card based on its rank and "trumpness"
		 */
		public int score(boolean isTrump) 
		{
			if (isTrump && _value == NINE._value) 
			{
				return 14;
			} 
			else if (isTrump && _value == JACK._value) 
			{
				return 20;
			}
			else
			{
				return scores[_value - 1];
			}
		}

		/**
		 * Convert the rank to its integer representation
		 * @return
		 */
		public int toInteger() 
		{
			return _value;
		}
		
		@Override
		public String toString()
		{
			return _name;
		}
	}

	/**
	 * Represents the suit of the card
	 * 
	 * @author Mischa Spelt
	 */
	public static class Suit
	{
		public static final Suit SPADES = new Suit(10, "spades");
		
		public static final Suit CLUBS = new Suit(20, "clubs");
		
		public static final Suit DIAMONDS = new Suit(30, "diamonds");
		
		public static final Suit HEARTS = new Suit(40, "hearts");

		/**
		 * Convert an integer value to a card suit, where
		 * 10 = spades, 20 = clubs, 30 = diamonds, 40 = hearts.
		 * 
		 * @param value
		 * 		The integer value to convert (10, 20, 30, 40)
		 * 
		 * @return
		 * 		The suit corresponding to the integer value
		 * 
		 * @throws IllegalArgumentException
		 * 		If value is not 10, 20, 30 or 40
		 */
		public static Suit fromInteger(int suit)
				throws IllegalArgumentException 
		{
			switch (suit) {
			case 10:
				return SPADES;
			case 20:
				return CLUBS;
			case 30:
				return DIAMONDS;
			case 40:
				return HEARTS;
			default:
				return null;
			}
		}

		/**
		 * The integer representation of the suit
		 */
		private final int _suit;

		/**
		 * The name of the suit
		 */
		private final String _name;

		/**
		 * Private constructor
		 * 
		 * @param ID
		 * 		The integer representation of the suit
		 * 
		 * @param name
		 * 		The name of the suit
		 */
		private Suit(int ID, String name) 
		{
			_suit = ID;
			this._name = name;
		}

		/**
		 * Convert the suit to its integer representation.
		 * 
		 * @return
		 * 		The integer (10, 20, 30, 40) representing the suit.
		 */
		public int toInteger()
		{
			return _suit;
		}

		@Override
		public String toString()
		{
			return _name;
		}
	}

	/**
	 * The suit of the card.
	 */
	private final Suit _suit;
	
	/**
	 * The rank of the card.
	 */
	private final CardRank _rank;

	/**
	 * Create a card from an integer representation (e.g. 13 = 9 of spades)
	 * 
	 * @param value
	 *            The integer value
	 */
	public Card(int value) 
	{
		this._suit = Suit.fromInteger(10 * (value / 10));
		this._rank = CardRank.fromInteger(value % 10);
	}

	/**
	 * Create a card given a suit and value
	 * 
	 * @param suit
	 *            The suit of the card
	 * @param value
	 *            The value of the card
	 */
	public Card(Suit suit, CardRank value)
	{
		this._suit = suit;
		this._rank = value;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		
		if (obj == null) 
		{
			return false;			
		}
		
		if (getClass() != obj.getClass()) 
		{
			return false;
		}
		
		final Card other = (Card) obj;
		if (_suit == null) 
		{
			if (other._suit != null) 
			{
				return false;
			}
		} 
		else if (_suit.toInteger() != other._suit.toInteger()) 
		{
			return false;
		}
		
		if (_rank == null) 
		{
			if (other._rank != null) 
			{
				return false;
			}
		} 
		else if (_rank.toInteger() != other._rank.toInteger()) 
		{
			return false;
		}
		
		return true;
	}

	/**
	 * Checks whether the current card exceeds another card, given the trump
	 * suit.
	 * 
	 * @param card
	 *            The card to check for
	 * 
	 * @param trumpSuit
	 *            The trump suit
	 * 
	 * @return Returns true if "this" would beat "other" given the trump suit,
	 *         OR if "other" is null.
	 */
	public boolean exceeds(Card other, Suit trumpSuit)
	{
		if (other == null)
		{
			return true;
		}

		// We can only exceed other if
		// a) we are the same color, but higher value
		// b) we are trump
		if (other._suit.equals(this._suit) || this.isTrump(trumpSuit))
		{
			final int myComparisonValue = this._rank.comparisonValue(this.isTrump(trumpSuit));
			final int theirComparisonValue = other._rank.comparisonValue(other.isTrump(trumpSuit));

			return (myComparisonValue > theirComparisonValue);
		} 
		else
		{
			return false;
		}
	}

	private boolean isTrump(Suit trumpSuit)
	{
		if(trumpSuit == null)
		{
			return false;
		}
		
		return this._suit.equals(trumpSuit);
	}

	/**
	 * Get the card suit
	 * 
	 * @return The suit of the card
	 */
	public Suit getSuit()
	{
		return this._suit;
	}

	/**
	 * Get the card value
	 * 
	 * @return The value of the card
	 */
	public CardRank getValue()
	{
		return this._rank;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + _suit.toInteger();
		result = prime * result + _rank.toInteger();
		return result;
	}

	/**
	 * Converts a card to its integer representation
	 * 
	 * @return A two-digit integer, the decade indicating the suit and the unit
	 *         indicating the value according to the Suit and CardValue classes.
	 */
	public int toInteger()
	{
		return _suit.toInteger() + _rank.toInteger();
	}

	@Override
	public String toString()
	{
		return _rank.toString() + " of " + _suit.toString();
	}
}
